/**
 * Axelor Business Solutions
 *
 * Copyright (C) 2017 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.axelor.apps.bankpayment.ebics.certificate;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Date;

import org.bouncycastle.jce.provider.BouncyCastleProvider;






//import org.kopi.ebics.interfaces.EbicsUser;
//import org.kopi.ebics.interfaces.PasswordCallback;
import com.axelor.apps.bankpayment.db.EbicsCertificate;
import com.axelor.apps.bankpayment.db.EbicsUser;
import com.axelor.apps.bankpayment.db.repo.EbicsCertificateRepository;
import com.axelor.apps.bankpayment.db.repo.EbicsUserRepository;
import com.axelor.apps.bankpayment.ebics.service.EbicsCertificateService;
import com.axelor.inject.Beans;

/**
 * Simple manager for EBICS certificates.
 *
 * @author hacheni
 *
 */
public class CertificateManager {

  public CertificateManager(EbicsUser user) {
    this.user = user;
    generator = new X509Generator();
  }

  /**
   * Creates the certificates for the user
   * @throws GeneralSecurityException
   * @throws IOException
   */
  public void create() throws GeneralSecurityException, IOException {
    
	  Calendar calendar;

    calendar = Calendar.getInstance();
    calendar.add(Calendar.YEAR, user.getEbicsPartner().getEbicsBank().getCertValidityPeriodSelect());

    org.apache.xml.security.Init.init();
    java.security.Security.addProvider(new BouncyCastleProvider());
    
    if(user.getEbicsTypeSelect() == EbicsUserRepository.EBICS_TYPE_T)  {
    	createA005Certificate(new Date(calendar.getTimeInMillis()));
    }
    createX002Certificate(new Date(calendar.getTimeInMillis()));
    createE002Certificate(new Date(calendar.getTimeInMillis()));
    setUserCertificates();
  }

  /**
   * Sets the user certificates
 * @throws IOException 
 * @throws CertificateEncodingException 
   */
  private void setUserCertificates() throws IOException, CertificateEncodingException {
	
    if(user.getEbicsTypeSelect() == EbicsUserRepository.EBICS_TYPE_T)  {
    	user.setA005Certificate(updateCertificate(a005Certificate, user.getA005Certificate(), a005PrivateKey.getEncoded(), EbicsCertificateRepository.TYPE_SIGNATURE));
    }   
    
	user.setX002Certificate(updateCertificate(x002Certificate, user.getX002Certificate(), x002PrivateKey.getEncoded(), EbicsCertificateRepository.TYPE_AUTHENTICATION));
    
	user.setE002Certificate(updateCertificate(e002Certificate, user.getE002Certificate(), e002PrivateKey.getEncoded(), EbicsCertificateRepository.TYPE_ENCRYPTION));

  }
  
  private EbicsCertificate updateCertificate(X509Certificate certificate,  EbicsCertificate cert, byte[] privateKey, String type) throws CertificateEncodingException, IOException {
	  
	  if (cert == null) {
		  cert = new EbicsCertificate();
		  cert.setTypeSelect(type);
	  }
	  
	  EbicsCertificateService certificateService = Beans.get(EbicsCertificateService.class);
	  
	  cert = certificateService.updateCertificate(certificate, cert);
	  
	  cert.setPrivateKey(privateKey);
	  
	  return cert;
	  
  }

  /**
   * Creates the signature certificate.
   * @param the expiration date of a the certificate.
   * @throws GeneralSecurityException
   * @throws IOException
   */
  public void createA005Certificate(Date end) throws GeneralSecurityException, IOException {
    KeyPair			keypair;

    keypair = KeyUtil.makeKeyPair(X509Constants.EBICS_KEY_SIZE);
    a005Certificate = generator.generateA005Certificate(keypair,
	                                                user.getDn(),
	                                                new Date(),
	                                                end,
	                                                user.getEbicsPartner().getEbicsBank().getUseX509ExtentionForAutoSignedCert());
    a005PrivateKey = keypair.getPrivate();
  }

  /**
   * Creates the authentication certificate.
   * @param the expiration date of a the certificate.
   * @throws GeneralSecurityException
   * @throws IOException
   */
  public void createX002Certificate(Date end) throws GeneralSecurityException, IOException {
    KeyPair			keypair;

    keypair = KeyUtil.makeKeyPair(X509Constants.EBICS_KEY_SIZE);
    x002Certificate = generator.generateX002Certificate(keypair,
	                                                user.getDn(),
	                                                new Date(),
	                                                end,
	                                                user.getEbicsPartner().getEbicsBank().getUseX509ExtentionForAutoSignedCert());
    x002PrivateKey = keypair.getPrivate();
  }

  /**
   * Creates the encryption certificate.
   * @param the expiration date of a the certificate.
   * @throws GeneralSecurityException
   * @throws IOException
   */
  public void createE002Certificate(Date end) throws GeneralSecurityException, IOException {
    KeyPair			keypair;

    keypair = KeyUtil.makeKeyPair(X509Constants.EBICS_KEY_SIZE);
    e002Certificate = generator.generateE002Certificate(keypair,
	                                                user.getDn(),
	                                                new Date(),
	                                                end,
	                                                user.getEbicsPartner().getEbicsBank().getUseX509ExtentionForAutoSignedCert());
    e002PrivateKey = keypair.getPrivate();
  }

  /**
   * Saves the certificates in PKCS12 format
   * @param path the certificates path
   * @param pwdCallBack the password call back
   * @throws GeneralSecurityException
   * @throws IOException
   */
  public void save(String path)
    throws GeneralSecurityException, IOException
  {
	char[] pwd = null;
	if ( user.getPassword() != null) {
	  pwd = user.getPassword().toCharArray();
	}
    writePKCS12Certificate(path + "/" + user.getUserId(), pwd);
  }

  /**
   * Loads user certificates from a given key store
   * @param path the key store path
   * @param pwdCallBack the password call back
   * @throws GeneralSecurityException
   * @throws IOException
   */
  public void load(String path)
    throws GeneralSecurityException, IOException
  {
    KeyStoreManager		loader;

    loader = new KeyStoreManager();
    
    if ( user.getPassword() != null) {
    	 loader.load(path, user.getPassword().toCharArray());
    }
    else {
    	loader.load(path , null);
    }
   
    a005Certificate = loader.getCertificate(user.getUserId() + "-A005");
    x002Certificate = loader.getCertificate(user.getUserId() + "-X002");
    e002Certificate = loader.getCertificate(user.getUserId() + "-E002");

    a005PrivateKey = loader.getPrivateKey(user.getUserId() + "-A005");
    x002PrivateKey = loader.getPrivateKey(user.getUserId() + "-X002");
    e002PrivateKey = loader.getPrivateKey(user.getUserId() + "-E002");
    setUserCertificates();
  }

  /**
   * Writes a the generated certificates into a PKCS12 key store.
   * @param filename the key store file name
   * @param password the key password
   * @throws IOException
   */
  public void writePKCS12Certificate(String filename, char[] password)
    throws GeneralSecurityException, IOException
  {
    if (filename == null || "".equals(filename)) {
      throw new IOException("The file name cannot be empty");
    }

    if (!filename.toLowerCase().endsWith(".p12")) {
      filename += ".p12";
    }

    FileOutputStream fos = new FileOutputStream(filename);
    writePKCS12Certificate(password, fos);
    fos.close();
  }

  /**
   * Writes a the generated certificates into a PKCS12 key store.
   * @param password the key store password
   * @param fos the output stream
   * @throws GeneralSecurityException
   * @throws IOException
   */
  public void writePKCS12Certificate(char[] password, OutputStream fos)
    throws GeneralSecurityException, IOException
  {
    KeyStore			keystore;

    keystore = KeyStore.getInstance("PKCS12", new BouncyCastleProvider());
    keystore.load(null, null);
    keystore.setKeyEntry(user.getUserId() + "-A005",
	                 a005PrivateKey,
	                 password,
	                 new X509Certificate[] {a005Certificate});
    keystore.setKeyEntry(user.getUserId() + "-X002",
	                 x002PrivateKey,
	                 password,
	                 new X509Certificate[] {x002Certificate});
    keystore.setKeyEntry(user.getUserId() + "-E002",
	                 e002PrivateKey,
	                 password,
	                 new X509Certificate[] {e002Certificate});
    keystore.store(fos, password);
  }

  // --------------------------------------------------------------------
  // DATA MEMBERS
  // --------------------------------------------------------------------

  private X509Generator					generator;
  private EbicsUser						user;

  private X509Certificate				a005Certificate;
  private X509Certificate				e002Certificate;
  private X509Certificate				x002Certificate;

  private PrivateKey					a005PrivateKey;
  private PrivateKey					x002PrivateKey;
  private PrivateKey					e002PrivateKey;
}
